/**
 * Copyright 2006 Brad Reynolds
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.leeloo.databinding.internal.ui;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Waits for a connection to a socket without holding up the caller.
 * 
 * @author Brad Reynolds
 */
public class ClientConnection extends Thread {
	private final ServerSocket serverSocket;

	private volatile boolean running;

	private Socket socket;

	private BufferedReader in;

	private final ITraceServerCallback callback;

	/**
	 * @param serverSocket
	 *            will be envoked when messages are returned. The invocation
	 *            will be from a thread that is not the originating thread.
	 * @param callback
	 */
	public ClientConnection(ServerSocket serverSocket, ITraceServerCallback callback) {
		if (serverSocket == null) {
			throw new IllegalArgumentException("Parameter " + serverSocket + " was null."); //$NON-NLS-1$ //$NON-NLS-2$
		}

		this.serverSocket = serverSocket;
		this.callback = callback;
	}
    
	public void run() {
		try {
			socket = serverSocket.accept();
			running = true;
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			LogMessage message = new LogMessage();
			
			Pattern endPattern = Pattern.compile("^\\s*</record>"); //$NON-NLS-1$

			ElementDescriptor[] elementDescriptors = new ElementDescriptor[7];
			elementDescriptors[0] = ElementDescriptor.asLong("millis", "milliseconds"); //$NON-NLS-1$ //$NON-NLS-2$
			elementDescriptors[1] = ElementDescriptor.asInt("sequence", "sequence"); //$NON-NLS-1$ //$NON-NLS-2$
			elementDescriptors[2] = ElementDescriptor.asString("logger", "loggerName"); //$NON-NLS-1$ //$NON-NLS-2$
			elementDescriptors[3] = ElementDescriptor.asString("class", "className");  //$NON-NLS-1$//$NON-NLS-2$
			elementDescriptors[4] = ElementDescriptor.asString("method", "methodName"); //$NON-NLS-1$ //$NON-NLS-2$
			elementDescriptors[5] = ElementDescriptor.asInt("thread", "threadID"); //$NON-NLS-1$ //$NON-NLS-2$
			elementDescriptors[6] = ElementDescriptor.asString("message", "message"); //$NON-NLS-1$ //$NON-NLS-2$
			
			while (running) {
				String line = in.readLine();

				if (line != null) {
					Matcher matcher = endPattern.matcher(line);
					if (matcher.matches()) {
						callback.message(message);
						message = new LogMessage();
						continue;
					}
					
					for (int i = 0; i < elementDescriptors.length; i++) {
						ElementDescriptor descriptor = elementDescriptors[i];
						matcher = descriptor.pattern.matcher(line);
						if (matcher.matches()) {
							assert matcher.groupCount() == 1 : "matcher.groupCount [" + matcher.groupCount() + "]";  //$NON-NLS-1$//$NON-NLS-2$
							String value = matcher.group(1);
							
							try {
								switch (descriptor.type) {
									case 0:
										descriptor.writeMethod.invoke(message, new Object[] {value});
										break;
									case 1:
										descriptor.writeMethod.invoke(message, new Object[] {new Integer(value)});
										break;
									case 2:
										descriptor.writeMethod.invoke(message, new Object[] {new Long(value)});
								}
							} catch (SecurityException e) {
								throw new RuntimeException(e);
							} catch (IllegalArgumentException e) {
								throw new RuntimeException(e);
							} catch (IllegalAccessException e) {
								throw new RuntimeException(e);
							} catch (InvocationTargetException e) {
								throw new RuntimeException(e);
							}
							
							break;
						}
					}
				}
			}
		} catch (SocketException e) {
			//Will be thrown when the socket is closed if the thread is blocked by an IO operation.
		} catch (IOException e) {
            throw new RuntimeException(e);
		}
	}

	public boolean isConnected() {
		return running;
	}

    public void interrupt() {
        super.interrupt();
        
        try {
            if (socket != null) {
                socket.close();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            socket = null;
            running = false;
            in = null;
        }
    }
    
    private static class ElementDescriptor {
        /**
         * Type of the property: 0 == String, 1 == int, 2 == long.
         */
        private final int type;
        private final Pattern pattern;
        private Method writeMethod;
        
        private ElementDescriptor (String elementName, String propertyName, int type) {
            this.type = type;
            this.pattern = Pattern.compile(new StringBuffer("^\\s*<").append(elementName).append(">(.*)</").append(elementName).append(">").toString());   //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
            try {
                this.writeMethod = new PropertyDescriptor(propertyName, LogMessage.class).getWriteMethod();
            } catch (IntrospectionException e) {
                e.printStackTrace();
            }
        }
        
        private static ElementDescriptor asString(String elementName, String propertyName) {
            return new ElementDescriptor(elementName, propertyName, 0);
        }
        
        private static ElementDescriptor asInt(String elementName, String propertyName) {
            return new ElementDescriptor(elementName, propertyName, 1);
        }
        
        private static ElementDescriptor asLong(String elementName, String propertyName) {
            return new ElementDescriptor(elementName, propertyName, 2);
        }
    }
}